/*
 * Copyright (c) 2007 iptelorg GmbH
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

/*!
 * \file
 * \brief Kamailio auth-identity :: Module interface
 * \ingroup auth-identity
 * Module: \ref auth-identity
 */

/*! \defgroup auth-identity Kamailio SIP identity support
 *
 * Auth Identity module provides functionalities for securely identifying
 * originators of SIP messages. This module has two basic service:
 *   - authorizer - authorizes a message and adds Identity and Identity-Info headers
 *   - verifier - verifies an authorized message
 *
 */


#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/sha.h>

#include <curl/curl.h>
#include <curl/easy.h>

#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../core/sr_module.h"
#include "../../core/mem/mem.h"
#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_cseq.h"
#include "../../core/parser/parse_content.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/parser/contact/parse_contact.h"
#include "../../core/timer.h"

#include "auth_identity.h"

MODULE_VERSION;

static int mod_init(void); /* Module initialization function */
static void mod_deinit(void);
static int add_identity(struct sip_msg *msg, char *srt1, char *str2);
static int get_certificate(struct sip_msg *msg, char *srt1, char *str2);
static int check_validity(struct sip_msg *msg, char *srt1, char *str2);
static int check_date(struct sip_msg *msg, char *srt1, char *str2);
static int check_callid(struct sip_msg *msg, char *srt1, char *str2);
static int date_proc(struct sip_msg *msg, char *srt1, char *str2);
static int check_certificate(struct sip_msg *msg, char *srt1, char *str2);
void callid_gc(unsigned int tick, void *param);

/*
 * Module parameter variables
 */
char *glb_sprivkeypath = ""; /* private key of the authentication service */
char *glb_sservercerturl =
		""; /* URL of the certificate of the authentication service */
char *glb_sservercertpath =
		""; /* Path of the certificate of the authentication service */
int glb_icertlimit = CERTIFICATE_TABLE_ITEM_LIMIT;
char *glb_scainfo = "";
int glb_iauthval =
		AUTH_MSG_VALIDITY_TIME; /* Message validity time in seconds (verification service)*/
int glb_imsgtime =
		AUTH_MSG_TO_AUTH_VALIDITY_TIME; /* Message validity time in seconds (authentication service)*/
int glb_icallidlimit = CALLID_TABLE_ITEM_LIMIT;

CURL *glb_hcurl; /* global cURL handle */
X509 *glb_pcertx509 = NULL;
X509_STORE *glb_cacerts = NULL;

RSA *glb_hmyprivkey = NULL; /* private key of the authentication service */
time_t glb_imycertnotafter = 0;

int glb_authservice_disabled = 0;
int glb_acceptpem = 0;

dynstr glb_sdgst = {{0, 0}, 0};			/* Digest string */
dynstr glb_sidentity = {{0, 0}, 0};		/* Identity message header */
dynstr glb_sidentityinfo = {{0, 0}, 0}; /* Identity-info message header */
dynstr glb_sdate = {{0, 0}, 0};			/* Date  message header */

dynstr glb_encedmsg = {{0, 0}, 0}; /* buffer for rsa encrypted string */
dynstr glb_b64encedmsg = {
		{0, 0}, 0}; /* buffer for base64, rsa encrypted string */

ttable *glb_tcert_table = 0; /* Certificate Table */
char glb_certisdownloaded = 0;
tcert_item glb_tcert = {{0, 0}, {0, 0}, 0}; /* Actually Used Certificate */

ttable *glb_tcallid_table = 0; /* Certificate Table */
typedef struct timeparams
{			   /* sturct of the callid garbage collector */
	int ibnow; /* the actual bucket we've not checked yet */
	int ibnum; /* number of the buckets we've to check */
	int ibcir; /* timer function's called this times during the whole table check */
} ttimeparams;
ttimeparams glb_ttimeparams = {0, 0, 0};

/*
 * Exported functions
 */
static cmd_export_t glb_cmds[] = {
		{"auth_date_proc", date_proc, 0, 0, 0, REQUEST_ROUTE},
		{"auth_add_identity", add_identity, 0, 0, 0, REQUEST_ROUTE},
		{"vrfy_get_certificate", get_certificate, 0, 0, 0, REQUEST_ROUTE},
		{"vrfy_check_msgvalidity", check_validity, 0, 0, 0, REQUEST_ROUTE},
		{"vrfy_check_certificate", check_certificate, 0, 0, 0, REQUEST_ROUTE},
		{"vrfy_check_date", check_date, 0, 0, 0, REQUEST_ROUTE},
		{"vrfy_check_callid", check_callid, 0, 0, 0, REQUEST_ROUTE},
		{0, 0, 0, 0, 0, 0}};


/*
 * Exported parameters
 */
static param_export_t glb_params[] = {
		{"privatekey_path", PARAM_STRING, &glb_sprivkeypath},
		{"certificate_url", PARAM_STRING, &glb_sservercerturl},
		{"certificate_cache_limit", PARAM_INT, &glb_icertlimit},
		{"callid_cache_limit", PARAM_INT, &glb_icallidlimit},
		{"certificate_path", PARAM_STRING, &glb_sservercertpath},
		{"auth_validity_time", PARAM_INT, &glb_iauthval},
		{"msg_timeout", PARAM_INT, &glb_imsgtime},
		{"cainfo_path", PARAM_STRING, &glb_scainfo},
		{"accept_pem_certs", PARAM_INT, &glb_acceptpem}, {0, 0, 0}};


/*
 * Module interface
 */
struct module_exports exports = {
		"auth_identity", DEFAULT_DLFLAGS, /* dlopen flags */
		glb_cmds,						  /* Exported functions */
		glb_params,						  /* Exported parameters */
		0,								  /* RPC methods */
		0,								  /* pseudo-variables exports */
		0,								  /* response function */
		mod_init,						  /* module initialization function */
		0,								  /* child initialization function */
		mod_deinit						  /* destroy function */
};


static int mod_init(void)
{
	CURLcode iRet;
	str sstr;
	FILE *hpemfile;
	char serr[160];
	X509 *pmycert = NULL; /* certificate of the authentication service */
	time_t tnow, ttmp;

	/*
	 *
	 * Parameter check
	 *
	 */
	if(glb_sprivkeypath[0] == 0) {
		LOG(L_WARN, "AUTH_IDENTITY:mod_init: Private key path is missing! "
					"Authorization service is disabled\n");
		glb_authservice_disabled = 1;
	}
	if(!glb_authservice_disabled && glb_sservercerturl[0] == 0) {
		LOG(L_WARN, "AUTH_IDENTITY:mod_init: URL of certificate of the server "
					"is missing! Authorization service is disabled\n");
		glb_authservice_disabled = 1;
	}
	if(!glb_authservice_disabled && glb_sservercertpath[0] == 0) {
		LOG(L_WARN, "AUTH_IDENTITY:mod_init: Path of certificate of the server "
					"is missing! Authorization service is disabled\n");
		glb_authservice_disabled = 1;
	}

	/*
	 *
	 * Init the curl session and download buffer
	 *
	 */
	curl_global_init(CURL_GLOBAL_ALL);
	if((glb_hcurl = curl_easy_init()) == NULL) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Unable to init cURL library!\n");
		return -1;
	}
	/* send all data to this function  */
	if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_WRITEFUNCTION, curlmem_cb))
			!= 0) {
		LOG(L_ERR,
				"AUTH_IDENTITY:mod_init: Unable to set cURL write function "
				"option: %s\n",
				curl_easy_strerror(iRet));
		return -2;
	}
	/* we pass our 'glb_tcert' struct to the callback function */
	if((iRet = curl_easy_setopt(
				glb_hcurl, CURLOPT_WRITEDATA, (void *)(&glb_tcert.scertpem)))
			!= 0) {
		LOG(L_ERR,
				"AUTH_IDENTITY:mod_init: Unable to set cURL writedata option: "
				"%s\n",
				curl_easy_strerror(iRet));
		return -4;
	}
	if(!(glb_tcert.scertpem.s = pkg_malloc(CERTIFICATE_LENGTH))) {
		PKG_MEM_ERROR;
		return -3;
	}
	/* some servers don't like requests that are made without a user-agent
	   field, so we provide one */
	if((iRet = curl_easy_setopt(
				glb_hcurl, CURLOPT_USERAGENT, NAME "-Agent/1.0"))
			!= 0) {
		LOG(L_WARN,
				"AUTH_IDENTITY:mod_init: Unable to set cURL useragent option: "
				"%s\n",
				curl_easy_strerror(iRet));
	}
	if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_SSL_VERIFYPEER, 1)) != 0) {
		LOG(L_WARN,
				"AUTH_IDENTITY:mod_init: Unable to set cURL verifypeer option: "
				"%s\n",
				curl_easy_strerror(iRet));
	}
	if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_SSL_VERIFYHOST, 2)) != 0) {
		LOG(L_WARN,
				"AUTH_IDENTITY:mod_init: Unable to set cURL verifyhost option: "
				"%s\n",
				curl_easy_strerror(iRet));
	}

	/* cainfo_path module parameter's been set */
	if(glb_scainfo[0]) {
		if((iRet = curl_easy_setopt(glb_hcurl, CURLOPT_CAINFO, glb_scainfo))
				!= 0) {
			LOG(L_WARN,
					"AUTH_IDENTITY:mod_init: Unable to set cURL cainfo option: "
					"%s\n",
					curl_easy_strerror(iRet));
		}
	}


	/*
	 *
	 * OpenSSL certificate verification initialization
	 *
	 */
	OpenSSL_add_all_algorithms();
	if(!(glb_cacerts = X509_STORE_new())) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to initialize X509 store\n");
		return -16;
	}
	if(X509_STORE_set_default_paths(glb_cacerts) != 1) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to set X509 store default "
				   "path\n");
		return -17;
	}
	if(glb_scainfo[0]
			&& X509_STORE_load_locations(glb_cacerts, glb_scainfo, NULL) != 1)
		LOG(L_WARN,
				"AUTH_IDENTITY:mod_init: unable to load X509 store location\n");


	/*
	 *
	 * Init the Date, Digest-String, Identity and Identity-Info
	 *
	 */
	if(initdynstr(&glb_sdgst, DGST_STR_INIT_SIZE))
		return -5;

	/*
	 * Init certificate table
	 */
	if(init_table(&glb_tcert_table, CERTIFICATE_TABLE_ENTRIES, glb_icertlimit,
			   cert_item_cmp, cert_item_init, cert_item_least, cert_item_free,
			   NULL))
		return -5;

	/*
	 * Init call-id table
	 */
	if(init_table(&glb_tcallid_table, CALLID_TABLE_ITEM_LIMIT, glb_icallidlimit,
			   cid_item_cmp, cid_item_init, cid_item_least, cid_item_free,
			   cid_item_gc))
		return -5;

	glb_ttimeparams.ibnow = 0;
	/* we've to check the whole table in glb_imsgtime, so the number of
	   buckets we've to check in every timer call is
	   CALLID_TABLE_ENTRIES/glb_imsgtime/CALLID_GARBAGE_COLLECTOR_INTERVAL */
	glb_ttimeparams.ibcir = glb_iauthval / CALLID_GARBAGE_COLLECTOR_INTERVAL;
	if(!glb_ttimeparams.ibcir)
		glb_ttimeparams.ibcir = 1;
	glb_ttimeparams.ibnum = CALLID_TABLE_ENTRIES / glb_ttimeparams.ibcir;

	if(register_timer(callid_gc, (void *)&glb_ttimeparams /* param*/,
			   CALLID_GARBAGE_COLLECTOR_INTERVAL /* period */)
			< 0) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Can not register timer\n");
		return -5;
	}

	/*
	 * If there were not enough parameter set then we could not initialize
	 * the authorizer part
	 */
	if(glb_authservice_disabled)
		return 0;


	if(initdynstr(&glb_sidentity, DGST_STR_INIT_SIZE))
		return -6;

	if(initdynstr(&glb_sdate, AUTH_TIME_LENGTH))
		return -7;

	if(initdynstr(&glb_sidentityinfo, AUTH_URL_LENGTH))
		return -8;

	/* we initialize indentity info header */
	sstr.s = IDENTITY_INFO_FIRST_PART;
	sstr.len = strlen(IDENTITY_INFO_FIRST_PART);
	if(cpy2dynstr(&glb_sidentityinfo, &sstr))
		return -9;
	sstr.s = glb_sservercerturl;
	sstr.len = strlen(glb_sservercerturl);
	if(app2dynstr(&glb_sidentityinfo, &sstr))
		return -10;
	sstr.s = IDENTITY_INFO_LAST_PART;
	/* we copy the trailing \0 because append_hf expects strings */
	sstr.len = strlen(IDENTITY_INFO_LAST_PART) + 1;
	if(app2dynstr(&glb_sidentityinfo, &sstr))
		return -11;

	/*
  	 * Get my certificate
	 */
	if(!(hpemfile = fopen(glb_sservercertpath, "r"))) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to open certificate '%s'\n",
				strerror(errno));
		return -12;
	}
	if(!(pmycert = PEM_read_X509(hpemfile, NULL, NULL, NULL))) {
		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: '%s'\n", serr);
		fclose(hpemfile);
		return -13;
	}
	if(x509_get_notafter(&glb_imycertnotafter, pmycert)) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Error getting certificate "
				   "expiration date\n");
		fclose(hpemfile);
		return -13;
	}
	if(x509_get_notbefore(&ttmp, pmycert)) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Error getting certificate validity "
				   "date\n");
		fclose(hpemfile);
		return -13;
	}
	if((tnow = time(0)) < 0) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: time error %s\n", strerror(errno));
		fclose(hpemfile);
		return -13;
	}
	if(tnow < ttmp || tnow > glb_imycertnotafter) {
		LOG(L_ERR,
				"AUTH_IDENTITY:mod_init: Date of certificate is invalid (%s)\n",
				glb_sservercertpath);
		fclose(hpemfile);
		return -14;
	}

	if(fclose(hpemfile))
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to close file\n");
	X509_free(pmycert);

	/*
	 *
 	 * Init RSA-SHA1 encoder
	 *
	 */
	hpemfile = fopen(glb_sprivkeypath, "r");
	if(!hpemfile) {
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to open private key '%s'\n",
				strerror(errno));
		return -12;
	}
	glb_hmyprivkey = PEM_read_RSAPrivateKey(hpemfile, NULL, NULL, NULL);
	if(!glb_hmyprivkey) {
		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: '%s'\n", serr);
		fclose(hpemfile);
		return -13;
	}
	if(fclose(hpemfile))
		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to close file\n");

	/* we encrypt the digest string hash to this buffer */
	if(initdynstr(&glb_encedmsg, RSA_size(glb_hmyprivkey)))
		return -14;

	/* we base64 encode the encrypted digest string hash to this buffer */
	if(initdynstr(&glb_b64encedmsg, (RSA_size(glb_hmyprivkey) / 3 + 1) * 4))
		return -15;

	return 0;
}


static void mod_deinit(void)
{
	curl_easy_cleanup(glb_hcurl);
	if(glb_tcert.scertpem.s)
		pkg_free(glb_tcert.scertpem.s);
	free_dynstr(&glb_sdgst);
	free_dynstr(&glb_sidentity);
	free_dynstr(&glb_sdate);
	free_table(glb_tcert_table);
	free_table(glb_tcallid_table);

	if(glb_cacerts)
		X509_STORE_free(glb_cacerts);
}


/*
 *
 *	VERIFIER FUNCTIONS
 *
 */


static int get_certificate(struct sip_msg *msg, char *srt1, char *str2)
{
	if(identityinfohdr_proc(&glb_tcert.surl, NULL, msg))
		return -3;

	/* we support rsa-sha1 only (alg.len==0 then we use rsa-sha1) */
	if(get_identityinfo(msg)->alg.len
			&& (get_identityinfo(msg)->alg.len != strlen("rsa-sha1")
					|| strncasecmp("rsa-sha1", get_identityinfo(msg)->alg.s,
							get_identityinfo(msg)->alg.len))) {
		LOG(L_ERR, "AUTH_IDENTITY:get_certificate: Unsupported Identity-Info "
				   "algorithm\n");
		return -5;
	}

	/* this case ivalidbefore==0 signs that this certificate was downloaded */
	glb_tcert.ivalidbefore = 0;

	/* check whether this certificate is our certificate table */
	if(get_cert_from_table(glb_tcert_table, &glb_tcert.surl, &glb_tcert)) {
		/* we did not found it in the table, so we've to download it */
		/* we reset the PEM buffer */
		glb_tcert.scertpem.len = 0;
		if(download_cer(&glb_tcert.surl, glb_hcurl))
			return -6;
		glb_certisdownloaded = 1;
	} else
		glb_certisdownloaded = 0;

	if(retrieve_x509(&glb_pcertx509, &glb_tcert.scertpem, glb_acceptpem))
		return -7;


	return 1;
}

/*
 * If the digest-string, assembled from the message, corresponds to the string
 * decoded from the Identity header by the acquired public key then the message
 * is valid. RFC 4474 [6] Step 3
 */
static int check_validity(struct sip_msg *msg, char *srt1, char *str2)
{
	str sidentity;
	char sencedsha[HASH_STR_SIZE];
	int iencedshalen;
#ifndef NEW_RSA_PROC
	char ssha[HASH_STR_SIZE];
#endif
	int ishalen;
	unsigned char sstrcrypted[SHA_DIGEST_LENGTH];
	int iRet = 1;


	if(!glb_pcertx509) {
		LOG(L_ERR, "AUTH_IDENTITY:check_validity: Certificate uninitialized! "
				   "(has vrfy_get_certificate been called?)\n");
		return -1;
	}

	do {
		/* get the value of identity header parsed */
		if(identityhdr_proc(&sidentity, NULL, msg)) {
			iRet = -1;
			break;
		}

		/* the length of identity value should be 172 octets long */
		if(sidentity.len > sizeof(sencedsha)) {
			LOG(L_ERR,
					"AUTH_IDENTITY:check_validity: Unexpected Identity length "
					"(%d)\n",
					sidentity.len);
			iRet = -2;
			break;
		}

		/* base64 decode the value of Identity header */
		base64decode(sidentity.s, sidentity.len, sencedsha, &iencedshalen);

		/* assemble the digest string to be able to compare it with decrypted one */
		if(digeststr_asm(&glb_sdgst, msg, NULL, AUTH_INCOMING_BODY)) {
			iRet = -5;
			break;
		}
		/* calculate hash */
		SHA1((unsigned char *)getstr_dynstr(&glb_sdgst).s,
				getstr_dynstr(&glb_sdgst).len, sstrcrypted);

#ifdef NEW_RSA_PROC
		/* decrypt with public key retrieved from the downloaded certificate
		   and compare it with the calculated digest hash */
		if(rsa_sha1_dec(sencedsha, iencedshalen, (char *)sstrcrypted,
				   sizeof(sstrcrypted), &ishalen, glb_pcertx509)) {
			iRet = -3;
			break;
		} else
			LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Identity OK\n");
#else
		/* decrypt with public key retrieved from the downloaded certificate */
		if(rsa_sha1_dec(sencedsha, iencedshalen, ssha, sizeof(ssha), &ishalen,
				   glb_pcertx509)) {
			iRet = -3;
			break;
		}

		/* check size */
		if(ishalen != sizeof(sstrcrypted)) {
			LOG(L_ERR,
					"AUTH_IDENTITY:check_validity: Unexpected decrypted hash "
					"length (%d != %d)\n",
					ishalen, SHA_DIGEST_LENGTH);
			iRet = -4;
			break;
		}
		/* compare */
		if(memcmp(sstrcrypted, ssha, ishalen)) {
			LOG(L_INFO, "AUTH_IDENTITY VERIFIER: comparing hashes failed -> "
						"Invalid Identity Header\n");
			iRet = -6;
			break;
		} else
			LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Identity OK\n");
#endif
	} while(0);

	glb_pcertx509 = NULL;

	return iRet;
}

/*
 * The Date header must indicate a time within 3600 seconds of the receipt of a
 * message. RFC 4474 [6] Step 4
 */
static int check_date(struct sip_msg *msg, char *srt1, char *str2)
{
	time_t tnow, tmsg;
	int ires;

	ires = datehdr_proc(NULL, NULL, msg);
	if(ires)
		return -1;


#ifdef HAVE_TIMEGM
	tmsg = timegm(&get_date(msg)->date);
#else
	tmsg = _timegm(&get_date(msg)->date);
#endif
	if(tmsg < 0) {
		LOG(L_ERR, "AUTH_IDENTITY:check_date: timegm error\n");
		return -2;
	}

	if((tnow = time(0)) < 0) {
		LOG(L_ERR, "AUTH_IDENTITY:check_date: time error %s\n",
				strerror(errno));
		return -3;
	}

	if(tnow > tmsg + glb_iauthval) {
		LOG(L_INFO,
				"AUTH_IDENTITY VERIFIER: Outdated date header value "
				"(%" TIME_T_FMT " sec)\n",
				TIME_T_CAST(tnow - tmsg + glb_iauthval));
		return -4;
	} else
		LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Date header value OK\n");

	return 1;
}


int check_certificate(struct sip_msg *msg, char *srt1, char *str2)
{
	struct sip_uri tfrom_uri;
	str suri;

	if(!glb_pcertx509) {
		LOG(L_ERR, "AUTH_IDENTITY:check_certificate: Certificate "
				   "uninitialized! (has vrfy_get_certificate been called?)\n");
		return -1;
	}
	/* this certificate was downloaded so we've to verify and add it to table */
	if(glb_certisdownloaded) {
		if(fromhdr_proc(&suri, NULL, msg))
			return -1;

		if(parse_uri(suri.s, suri.len, &tfrom_uri)) {
			LOG(L_ERR, "AUTH_IDENTITY:get_certificate: Error while parsing "
					   "FROM URI\n");
			return -2;
		}

		if(verify_x509(glb_pcertx509, glb_cacerts))
			return -3;

		if(check_x509_subj(glb_pcertx509, &tfrom_uri.host))
			return -4;

		/* we retrieve expiration date from the certificate (it needs for
		   certificate table garbage collector) */
		if(x509_get_notafter(&glb_tcert.ivalidbefore, glb_pcertx509))
			return -5;

		if(addcert2table(glb_tcert_table, &glb_tcert))
			return -6;
	}
	return 1;
}

static int check_callid(struct sip_msg *msg, char *srt1, char *str2)
{
	str scid, sftag, scseqnum;
	unsigned int ucseq;
	int ires;
	time_t ivalidbefore;


	if(callidhdr_proc(&scid, NULL, msg))
		return -1;

	if(cseqhdr_proc(&scseqnum, NULL, msg))
		return -2;
	if(str2int(&scseqnum, &ucseq))
		return -3;

	if(fromhdr_proc(NULL, &sftag, msg))
		return -4;

	if((ivalidbefore = time(0)) < 0) {
		LOG(L_ERR, "AUTH_IDENTITY:check_callid: time error %s\n",
				strerror(errno));
		return -5;
	}

	ires = proc_cid(glb_tcallid_table, &scid, &sftag, ucseq,
			ivalidbefore + glb_iauthval);
	if(ires) {
		if(ires == AUTH_FOUND)
			LOG(L_INFO, "AUTH_IDENTITY VERIFIER: Call is replayed!\n");
		return -6;
	}

	return 1;
}


void callid_gc(unsigned int tick, void *param)
{
	/* check the last slice */
	if(((ttimeparams *)param)->ibnow + 1 == ((ttimeparams *)param)->ibcir) {
		garbage_collect(glb_tcallid_table,
				(((ttimeparams *)param)->ibnow) * ((ttimeparams *)param)->ibnum,
				CALLID_TABLE_ENTRIES - 1);
		/* we step to the first slice */
		((ttimeparams *)param)->ibnow = 0;
	} else {
		garbage_collect(glb_tcallid_table,
				(((ttimeparams *)param)->ibnow) * ((ttimeparams *)param)->ibnum,
				((((ttimeparams *)param)->ibnow + 1)
						* ((ttimeparams *)param)->ibnum)
						- 1);
		/* we step to the next slice */
		((ttimeparams *)param)->ibnow++;
	}
}

/*
 *
 *	AUTHORIZER FUNCTIONS
 *
 */

/* Checks the Date header of the message. RFC4474 [5] Step 3 */
static int date_proc(struct sip_msg *msg, char *srt1, char *str2)
{
	str sdate;
	int iRes;
	time_t tmsg, tnow;

	if(glb_authservice_disabled) {
		LOG(L_WARN, "AUTH_IDENTITY:date_proc: Authentication Service is "
					"disabled\n");
		return -1;
	}

	getstr_dynstr(&glb_sdate).len = 0;

	/* we'd like to get the DATE header of the message */
	iRes = datehdr_proc(&sdate, NULL, msg);
	switch(iRes) {
		case AUTH_ERROR:
			return -1;
		case AUTH_NOTFOUND:
			if(append_date(
					   &getstr_dynstr(&glb_sdate), glb_sdate.size, &tmsg, msg))
				return -2;
			break;
		/* Message has Date header so we check that */
		case AUTH_OK:
#ifdef HAVE_TIMEGM
			tmsg = timegm(&get_date(msg)->date);
#else
			tmsg = _timegm(&get_date(msg)->date);
#endif
			if(tmsg < 0) {
				LOG(L_ERR, "AUTH_IDENTITY:date_proc: timegm error\n");
				return -3;
			}
			if((tnow = time(NULL)) < 0) {
				LOG(L_ERR, "AUTH_IDENTITY:date_proc: time error\n");
				return -4;
			}
			/*
			 * If the value of this field contains a time different by more than
			 * ten minutes from the current time noted by the authentication
			 * service then it should reject the message.
			 */
			if(tmsg + glb_imsgtime < tnow || tnow + glb_imsgtime < tmsg) {
				LOG(L_INFO, "AUTH_IDENTITY AUTHORIZER: Date header overdue\n");
				return -6;
			}
			break;
		default:
			/* unknown result */
			return -7;
	}

	/*
	 * The authentication service MUST verify that the Date header
	 * falls within the validity period of its certificate
	 * RFC 4474 [6] Step 3
	 */
	if(glb_imycertnotafter < tmsg) {
		LOG(L_INFO, "AUTH_IDENTITY AUTHORIZER: My certificate has expired\n");
		return -8;
	}

	return 1;
}

/*
 * Concates the message From, To, Call-ID, Cseq, Date,  Contact header fields
 * and the message body to digest-string, signs with the domain private-key,
 * BASE64 encodes that, and finally adds it to the message as the 'Identity'
 * header value. RFC4474 [5] Step 4
 *
 * Adds Identity-Info header to the message which contains an URI from which
 * its certificate can be acquired. RFC4474 [5] Step 4
 */
static int add_identity(struct sip_msg *msg, char *srt1, char *str2)
{
	int iRes;
	str sstr;


	if(glb_authservice_disabled) {
		LOG(L_WARN, "AUTH_IDENTITY:add_identity: Authentication Service is "
					"disabled\n");
		return -1;
	}

	/* check Date */
	iRes = datehdr_proc(NULL, NULL, msg);
	switch(iRes) {
		case AUTH_ERROR:
			return -1;
		case AUTH_NOTFOUND:
			if(!getstr_dynstr(&glb_sdate).len) {
				/*
				 * date_proc() must be called before add_identity() because
				 * that function initializes the Date if that not exists
				 * in the SIP message
				 */
				LOG(L_ERR, "AUTH_IDENTITY:add_identity: Date header is not "
						   "found (has auth_date_proc been called?)\n");
				return -2;
			}
			/*  assemble the digest string and the DATE header is missing in the original message */
			if(digeststr_asm(&glb_sdgst, msg, &getstr_dynstr(&glb_sdate),
					   AUTH_OUTGOING_BODY | AUTH_ADD_DATE))
				return -3;
			break;
		default:
			/*  assemble the digest string and the DATE header is available in the message */
			if(digeststr_asm(&glb_sdgst, msg, NULL, AUTH_OUTGOING_BODY))
				return -4;
			break;
	}

	/* calculate the SHA1 hash and encrypt with our provate key */
	if(rsa_sha1_enc(
			   &glb_sdgst, &glb_encedmsg, &glb_b64encedmsg, glb_hmyprivkey))
		return -5;

	/* we assemble the value of the Identity haader */
	sstr.s = IDENTITY_FIRST_PART;
	sstr.len = strlen(IDENTITY_FIRST_PART);
	if(cpy2dynstr(&glb_sidentity, &sstr))
		return -6;

	if(app2dynstr(&glb_sidentity, &getstr_dynstr(&glb_b64encedmsg)))
		return -7;

	sstr.s = IDENTITY_LAST_PART;
	/* +1 : we need the trailing \0 character too */
	sstr.len = strlen(IDENTITY_LAST_PART) + 1;
	if(app2dynstr(&glb_sidentity, &sstr))
		return -8;

	if(append_hf(msg, getstr_dynstr(&glb_sidentity).s, HDR_IDENTITY_T))
		return -9;

	if(append_hf(msg, getstr_dynstr(&glb_sidentityinfo).s, HDR_IDENTITY_INFO_T))
		return -10;

	return 1;
}
